package cn.highedu.http;


import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * 响应对象， 用于封装HTTP响应信息
 * 调用send方法就会将响应信息拼接为HTTP响应，
 * 发送到浏览器
 */
public class HttpServletResponse {
    /** 与客户端浏览器的连接通道 */
    private Socket socket;

    /** 向客户端发送的响应状态码， 默认值是200 */
    private int statusCode = 200;

    /** 向客户端发送的响应状态原因，默认值 OK，表示正常 */
    private String statusReason = "OK";

    /** 向客户端响应的文件，发送时候放到响应正文（Body）中发送 */
    private File contentFile;

    /**
     * 存储动态显示内容，发送时候，如果contentFile是null，就发送content数组的内容
     */
    private byte[] content;

    /**
     * 用于查询Content Type 类型的API
     */
    private static MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();

    /**
     * 缓存向客户端发送的全部响应头
     */
    private final HashMap<String, String> headers = new HashMap<>();

    public HttpServletResponse(Socket socket){
        this.socket = socket;
    }

    /**
     * 添加响应头，添加到响应头的集合中， 发送时候把集合中全部的头发送到浏览器
     * @param name 响应头的名称， 如： Content-Type
     * @param value 响应头的值， 如： text/html
     */
    public void addHeader(String name, String value){
       headers.put(name, value);
    }

    /**
     * 发送响应头，遍历响应头集合，把每个响应头都发出去
     */
    private void sendHeaders() throws IOException{
        for (Map.Entry<String, String> entry: headers.entrySet()){
            String name = entry.getKey();
            String value = entry.getValue();
            println(name + ": " + value);
            System.out.println("发送响应头：" + name + ": " + value);
        }
        println(""); //响应头之后发送空行
    }

    /**
     * 修改响应的状态码，比如文件没有找到时候 改成 404
     * @param statusCode 需要修改的状态码，请参阅RFC2616
     */
    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    /**
     * 修改响应状态原因，比如状态码404时候，应该设置为 Not Found
     * @param statusReason 响应状态原因
     */
    public void setStatusReason(String statusReason) {
        this.statusReason = statusReason;
    }

    /**
     * 这是发送到浏览器的文件，文件会放到响应正文区域发送
     * @param contentFile 需要发送的文件
     */
    public void setContentFile(File contentFile) {
        this.contentFile = contentFile;
        //添加响应头
        addHeader("Content-Type", fileTypeMap.getContentType(contentFile));
        addHeader("Content-Lenght", Long.toString(contentFile.length()));
        addHeader("File-Name", contentFile.getName());
    }

    private static final char CR = '\r';
    private static final char LF = '\n';
    /**
     * 向客户端发送一行文本信息，发送时候采用 ASC 编码，发送后会添加
     * CRLF为结尾。可以用于发送 状态行 或者 响应头信息
     * @param line 一行文本信息
     * @throws IOException 网络故障
     */
    private void println(String line) throws IOException{
        OutputStream out = socket.getOutputStream();
        byte[] bytes = line.getBytes(StandardCharsets.ISO_8859_1);
        out.write(bytes);
        out.write(CR);
        out.write(LF);
    }

    /**
     * 发送方法，将状态行，响应头，响应实体，依次发送到客户端浏览器
     * @throws IOException 网络故障
     */
    public void send() throws IOException{
        //发送状态行
        sendStatusLine();

        //调用发送请求头的方法
        sendHeaders();

        //发送响应实体（body）内容
        sendContent();
    }

    private void sendStatusLine() throws IOException {
        String statusLine = "HTTP/1.1 " + statusCode + " " + statusReason;
        println(statusLine);
        System.out.println("发送状态行:"+statusLine);
    }

    public void setContent(String html) {
        this.content = html.getBytes(StandardCharsets.UTF_8);
        addHeader("Content-Type", "text/html; charset=utf-8");
        addHeader("Content-Length", Integer.toString(content.length));
    }

    private void sendContent() throws IOException {
        OutputStream out = socket.getOutputStream();
        if (content != null) {
            out.write(content);
            System.out.println("发送数组正文！");
        }else {
            FileInputStream in = new FileInputStream(contentFile);
            byte[] buf = new byte[8 * 1024];
            int n;
            while ((n = in.read(buf)) != -1) {
                out.write(buf, 0, n);
            }
            in.close();
            out.flush();
        }
    }


}






